--------------------------------------------------------------------------------
-- Array Support
-- Original author: Unknown (But the Wiki mentions Erkle)
-- Rewritten by Divran at 2010-12-21
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- Helper functions and constants
--------------------------------------------------------------------------------

local table_insert = table.insert
local table_remove = table.remove
local floor = math.floor

E2_MAX_ARRAY_SIZE = 1024*1024	-- 1MB

local blocked_types = {
	["t"] = true,
	["r"] = true,
	["xgt"] = true
}

-- Fix return values
local function fixdef( val )
	return (type(val) == "table") and table.Copy(val) or val
end

-- Uppercases the first letter
local function upperfirst( word )
	return word:Left(1):upper() .. word:Right(-2):lower()
end

--------------------------------------------------------------------------------
-- Type
--------------------------------------------------------------------------------

registerType("array", "r", {},
	function(self, input)
		local ret = {}
		self.prf = self.prf + #input / 3
		for k,v in pairs(input) do ret[k] = v end
		return ret
	end,
	nil,
	function(retval)
		if type(retval) ~= "table" then error("Return value is not a table, but a "..type(retval).."!",0) end
	end,
	function(v)
		return type(v) ~= "table"
	end
)

--------------------------------------------------------------------------------
-- Array(...)
-- Constructs and returns an array with the given values as elements. If you specify values that are not supported by the array data type, they are skipped
--------------------------------------------------------------------------------
__e2setcost(1)
e2function array array(...)
	local ret = {...}
	if (#ret == 0) then return {} end -- This is in place of the old "array()" function (now deleted because array(...) overwrote it)
	for k,v in pairs( ret ) do
		self.prf = self.prf + 1/3
		if (blocked_types[typeids[k]]) then ret[k] = nil end
	end
	return ret
end

registerOperator( "kvarray", "", "r", function( self, args )
	local ret = {}
	
	local values = args[2]
	local types = args[3]
	
	for k,v in pairs( values ) do
		if not blocked_types[types[k]] then
			local key = k[1]( self, k )
			local value = v[1]( self, v )
			
			ret[key] = value
			
			self.prf = self.prf + 1/3
		end
	end
	
	return ret
end)

--------------------------------------------------------------------------------
-- = operator
--------------------------------------------------------------------------------
e2function array operator=(array lhs, array rhs)

	local lookup = self.data.lookup
	
	-- remove old lookup entry
	if (lookup[rhs]) then lookup[rhs][lhs] = nil end
	
	-- add new
	if (!lookup[rhs]) then
		lookup[rhs] = {}
	end
	lookup[rhs][lhs] = true

	self.vars[lhs] = rhs
	self.vclk[lhs] = true
	return rhs
end

--------------------------------------------------------------------------------
-- IS operator
--------------------------------------------------------------------------------
e2function number operator_is(array arr)
	return type(arr) == "table" and 1 or 0
end

--------------------------------------------------------------------------------
-- Looped functions and operators
--------------------------------------------------------------------------------
registerCallback( "postinit", function()
	local getf, setf
	for k,v in pairs( wire_expression_types ) do
		local name = k:lower()
		if (name == "normal") then name = "number" end
		local nameupperfirst = upperfirst( name )
		local id = v[1]
		local default = v[2]
		local typecheck = v[6]
		
		if (!blocked_types[id]) then -- blocked check start
		
			--------------------------------------------------------------------------------
			-- Get functions
			-- value = R[N,type], and value = R:<type>(N)
			--------------------------------------------------------------------------------
			__e2setcost(10)
			
			local function getter( self, array, index, doremove )
				if (!array or !index) then return fixdef( default ) end -- Make sure array and index are value
				local ret
				if (doremove) then
					ret = table_remove( array, index )
					self.vclk[array] = true
				else
					ret = array[floor(index)]
				end
				if (typecheck and typecheck( ret )) then return fixdef( default ) end -- If typecheck returns true, the type is wrong.
				return ret
			end
			
			registerOperator("idx", id.."=rn", id, function(self,args)
				local op1, op2 = args[2], args[3]
				local array, index = op1[1](self,op1), op2[1](self,op2)
				return getter( self, array, index )
			end)
			
			registerFunction( name, "r:n", id, function(self,args)
				local op1, op2 = args[2], args[3]
				local array, index = op1[1](self,op1), op2[1](self,op2)
				return getter( self, array, index )
			end)
			
			--------------------------------------------------------------------------------
			-- Set functions
			-- R[N,type] = value, and R:set<type>(N,value)
			--------------------------------------------------------------------------------

			local function setter( self, array, index, value, doinsert )
				if (!array or !index) then return fixdef( default ) end -- Make sure array and index are valid
				if (typecheck and typecheck( value )) then return fixdef( default ) end -- If typecheck returns true, the type is wrong.
				if (doinsert) then
					if (#array + 1 > E2_MAX_ARRAY_SIZE) then return fixdef( default ) end -- Check max size
					table_insert( array, index, value )
				else
					if (index > E2_MAX_ARRAY_SIZE) then return fixdef( default ) end -- Check max size
					array[floor(index)] = value
				end
				self.vclk[array] = true
				return value
			end
			
			registerOperator("idx", id.."=rn"..id, id, function(self,args)
				local op1, op2, op3 = args[2], args[3], args[4]
				local array, index, value = op1[1](self,op1), op2[1](self,op2), op3[1](self,op3)
				return setter( self, array, index, value )
			end)
			
			registerFunction("set" .. nameupperfirst, "r:n"..id, id, function(self,args)
				local op1, op2, op3 = args[2], args[3], args[4]
				local array, index, value = op1[1](self,op1), op2[1](self,op2), op3[1](self,op3)
				return setter( self, array, index, value )
			end)
			
			
			--------------------------------------------------------------------------------
			-- Push functions
			-- Inserts the value at the end of the array
			--------------------------------------------------------------------------------
			__e2setcost(15)
			
			registerFunction( "push" .. nameupperfirst, "r:" .. id, id, function(self,args)
				local op1, op2 = args[2], args[3]
				local array, value = op1[1](self,op1), op2[1](self,op2)
				return setter( self, array, #array + 1, value )
			end)
			
			--------------------------------------------------------------------------------
			-- Insert functions
			-- Inserts the value at the specified index. Subsequent values are moved up to compensate.
			--------------------------------------------------------------------------------
			registerFunction( "insert" .. nameupperfirst, "r:n" .. id, id, function( self, args )
				local op1, op2, op3 = args[2], args[3], args[4]
				local array, index, value = op1[1](self,op1), op2[1](self,op2), op3[1](self,op3)
				return setter( self, array, index, value, true )
			end)
			
			--------------------------------------------------------------------------------
			-- Pop functions
			-- Removes and returns the last value in the array.
			--------------------------------------------------------------------------------
			registerFunction( "pop" .. nameupperfirst, "r:", id, function(self,args)
				local op1 = args[2]
				local array = op1[1](self,op1)
				if (!array) then return fixdef( default ) end
				return getter( self, array, #array, true )
			end)
			
			--------------------------------------------------------------------------------
			-- Unshift functions
			-- Inserts the value at the beginning of the array. Subsequent values are moved up to compensate.
			--------------------------------------------------------------------------------
			registerFunction( "unshift" .. nameupperfirst, "r:" .. id, id, function(self,args)
				local op1, op2 = args[2], args[3]
				local array, value = op1[1](self,op1), op2[1](self,op2)
				return setter( self, array, 1, value, true )
			end)
			
			--------------------------------------------------------------------------------
			-- Shift functions
			-- Removes and returns the first value of the array. Subsequent values are moved down to compensate.
			--------------------------------------------------------------------------------
			registerFunction( "shift" .. nameupperfirst, "r:", id, function(self,args)
				local op1 = args[2]
				local array = op1[1](self,op1)
				if (!array) then return fixdef( default ) end
				return getter( self, array, 1, true )
			end)
			
			--------------------------------------------------------------------------------
			-- Remove functions
			-- Removes and returns the specified value of the array. Subsequent values are moved down to compensate.
			--------------------------------------------------------------------------------
			registerFunction( "remove" .. nameupperfirst, "r:n", id, function(self,args)
				local op1, op2 = args[2], args[3]
				local array, index = op1[1](self,op1), op2[1](self,op2)
				if (!array or !index) then return fixdef( default ) end
				return getter( self, array, index, true )
			end)
		
		
		end -- blocked check end
	end
end)

--------------------------------------------------------------------------------
-- Pop
-- Removes the last entry in the array
--------------------------------------------------------------------------------
__e2setcost(15)
e2function void array:pop()
	table_remove( this )
	self.vclk[this] = true
end

--------------------------------------------------------------------------------
-- Remove
-- Removes the specified entry in the array
--------------------------------------------------------------------------------
__e2setcost(15)
e2function void array:remove( index )
	table_remove( this, index )
	self.vclk[this] = true
end

--------------------------------------------------------------------------------
-- Shift
-- Removes the first entry in the array
--------------------------------------------------------------------------------
__e2setcost(15)
e2function void array:shift()
	table_remove( this, 1 )
	self.vclk[this] = true
end

--------------------------------------------------------------------------------
-- Exists
-- Returns 1 if any value exists at the specified index, 0 if not
--------------------------------------------------------------------------------
__e2setcost(1)
e2function number array:exists( index )
	return this[index] != nil and 1 or 0
end

--------------------------------------------------------------------------------
-- Count
-- Returns the number of entries in the array
--------------------------------------------------------------------------------
__e2setcost(5)
e2function number array:count()
	return #this
end

--------------------------------------------------------------------------------
-- Clear
-- Empties the array
--------------------------------------------------------------------------------
__e2setcost(1)
e2function void array:clear()
	self.prf = self.prf + #this / 3
	table.Empty(this)
end

--------------------------------------------------------------------------------
-- Clone
-- Returns a copy of the array
--------------------------------------------------------------------------------
__e2setcost(1)
e2function array array:clone()
	local ret = {}
	self.prf = self.prf + #this / 3
	for k,v in pairs(this) do
		ret[k] = v
	end
	return ret
end

--------------------------------------------------------------------------------
-- Sum
-- Returns the sum of all numerical values in the array
--------------------------------------------------------------------------------
__e2setcost(1)
e2function number array:sum()
	local ret = 0
	local indexes = 0
	for k,v in pairs( this ) do
		indexes = indexes + 1
		ret = ret + (tonumber(v) or 0)
	end
	self.prf = self.prf + indexes / 2
	return ret
end

--------------------------------------------------------------------------------
-- Average
-- Returns the average of all numerical values in the array
--------------------------------------------------------------------------------
__e2setcost(1)
e2function number array:average()
	local ret = 0
	local indexes = 0
	for k,v in pairs( this ) do
		indexes = indexes + 1
		ret = ret + (tonumber(v) or 0)
	end
	self.prf = self.prf + indexes / 2
	return ret / indexes
end

--------------------------------------------------------------------------------
-- Min
-- Returns the smallest value in the array
--------------------------------------------------------------------------------
__e2setcost(1)
e2function number array:min()
	local num
	local indexes = 0
	for k,v in pairs( this ) do
		indexes = indexes + 1
		local val = tonumber(v) or 0
		if (num == nil or val < num) then
			num = val
		end
	end
	self.prf = self.prf + indexes / 2
	return num or 0
end

--------------------------------------------------------------------------------
-- MinIndex
-- Returns the index of the smallest value in the array
--------------------------------------------------------------------------------
__e2setcost(1)
e2function number array:minIndex()
	local num = nil
	local index = nil
	local indexes = 0
	for k,v in pairs( this ) do
		indexes = indexes + 1
		local val = tonumber(v) or 0
		if (num == nil or v < num) then
			num = v
			index = k
		end
	end
	self.prf = self.prf + indexes / 2
	return index or 0
end

--------------------------------------------------------------------------------
-- Max
-- Returns the largest value in the array
--------------------------------------------------------------------------------
__e2setcost(1)
e2function number array:max()
	local num
	local indexes = 0
	for k,v in pairs( this ) do
		indexes = indexes + 1
		local val = tonumber(v) or 0
		if (num == nil or val > num) then
			num = val
		end
	end
	self.prf = self.prf + indexes / 2
	return num or 0
end

--------------------------------------------------------------------------------
-- MaxIndex
-- Returns the index of the largest value in the array
--------------------------------------------------------------------------------
__e2setcost(1)
e2function number array:maxIndex()
	local num = nil
	local index = nil
	local indexes = 0
	for k,v in pairs( this ) do
		indexes = indexes + 1
		local val = tonumber(v) or 0
		if (num == nil or v > num) then
			num = v
			index = k
		end
	end
	self.prf = self.prf + indexes / 2
	return index or 0
end

--------------------------------------------------------------------------------
-- Concat
-- Concatenatest the values of the array
--------------------------------------------------------------------------------
__e2setcost(1)
e2function string array:concat()
	local out = ""
	
	self.prf = self.prf + #this
	
	for _,value in ipairs(this) do
		out = out .. tostring(value)
	end
	return out
end

e2function string array:concat(string delimiter)
	local out = ""
	
	self.prf = self.prf + #this
	
	for _,value in ipairs(this) do
		out = out .. tostring(value) .. delimiter
	end
	return string.Left(out, string.len(out) - string.len(delimiter))
end

--------------------------------------------------------------------------------
-- Id
-- Returns a string identifier representing the array
--------------------------------------------------------------------------------
__e2setcost(1)
e2function string array:id()
	return tostring(this)
end

--------------------------------------------------------------------------------
-- Add
-- Add the contents of the specified array to the end of 'this'
--------------------------------------------------------------------------------
__e2setcost(1)
e2function array array:add( array other )
	if (!next(this) or !next(other)) then return {} end -- One of them is empty
	local ret = {}
	for i=1,#this do
		ret[i] = this[i]
		self.prf = self.prf + 0.3
	end
	for i=1,#other do
		ret[#ret+1] = other[i]
		self.prf = self.prf + 0.3
	end
	return ret
end

--------------------------------------------------------------------------------
-- Merge
-- Merges the two tables. Identical indexes will be overwritten by 'other'
--------------------------------------------------------------------------------
__e2setcost(1)
e2function array array:merge( array other )
	if (!next(this) or !next(other)) then return {} end -- One of them is empty
	local ret = {}
	for i=1,math.max(#this,#other) do
		ret[i] = other[i] or this[i]
		self.prf = self.prf + 0.3
	end
	return ret
end

__e2setcost(nil)